package logic;

import java.util.Vector;

import data.AlgorithmInput;
import data.Data;
import data.GeneralDefinitions.DiskType;
import data.GraphData;
import data.MirroringGDWithBS;
import data.MirroringGDWithoutBS;
import data.MirroringInput;
import data.TimeUnit;

public class MirroringAlgorithm extends Algorithm {

    private final double writeFromCacheSize = 50000;
    private final double energyPerTimeUnit = 20;

    
 

    public GraphData compute(AlgorithmInput input, Data data) {
        
   	
        MirroringInput in = (MirroringInput) input;
        GraphData ans = null;
        if (in.get_bufferSize() != null) {
            ans = computeWithBufferSize(in, data);
        } else {
            ans = computeWithoutBufferSize(in, data);
        }
        return ans;
    }

    private GraphData computeWithoutBufferSize(MirroringInput in, Data data) {

        Vector<MirroringGDWithBS> vec = new Vector<MirroringGDWithBS>();
        double disksize = getDiskSize(in.get_diskType(), data);
        double buffersize;
        for (int i = 1; i <= 20; i++) {
            buffersize = i * disksize;
            vec.add((MirroringGDWithBS) computeWithBufferSize(buffersize, in.get_diskType(), data));
        }
        MirroringGDWithoutBS ans = new MirroringGDWithoutBS(vec);
        return ans;
    }

    private GraphData computeWithBufferSize(MirroringInput in, Data data) {
        return computeWithBufferSize(in.get_bufferSize(), in.get_diskType(), data);
    }

    private GraphData computeWithBufferSize(double bufferSize, DiskType diskType, Data data) {
        Vector<Integer> timeunitsOn = unitsOn(bufferSize, data);
        if (timeunitsOn == null) {
            return new MirroringGDWithBS(bufferSize);
        }
        double price = getPrice(bufferSize, diskType);

        if (price < 0) //TODO throw exception
        {
            return  new MirroringGDWithBS(bufferSize);
        }
        double energy = timeunitsOn.size() * energyPerTimeUnit;
        MirroringGDWithBS ans =
                new MirroringGDWithBS(bufferSize, price, timeunitsOn, energy, data.getNumOfTimeUnits());
        return ans;
    }

    private double getPrice(double bufferSize, DiskType diskType) {
        Double damount;
        int amount;
        switch (diskType) {
            case SATA:
                damount = bufferSize / Data.SATADiskSize;
                amount = damount.intValue();
                if (damount - amount > 0) {
                    amount += 1;
                }
                return amount * Data.SATAPRICE;
            case SCSI:
                damount = bufferSize / Data.SCSIDiskSize;
                amount = damount.intValue();
                if (damount - amount > 0) {
                    amount += 1;
                }
                return amount * Data.SCSIPRICE;
            case SSD:
                damount = bufferSize / Data.SSDDiskSize;
                amount = damount.intValue();
                if (damount - amount > 0) {
                    amount += 1;
                }
                return amount * Data.SSDPRICE;
        }
        return -1;
    }

    private double getDiskSize(DiskType diskType, Data data) {
        switch (diskType) {
            case SATA:
                return Data.SATADiskSize;
            case SCSI:
                return Data.SCSIDiskSize;
            case SSD:
                return Data.SSDDiskSize;
        }
        return -1;

    }

    private Vector<Integer> unitsOn(double bufferSize, Data data) {

        double kbwritten = 0;
    	//double kbwritten = bufferSize/2;
        
    	
        Vector<Integer> timeUnitsOn = new Vector<Integer>();
        boolean on = false;

        for (int i = 0; i < data.getNumOfTimeUnits(); i++) {
            TimeUnit tu = data.getTimeUnit(i);
            kbwritten += tu.get_totalKbWritten();           
            int tu_size=tu.get_timesize();

            if (on) {
                timeUnitsOn.add(i);
                kbwritten = Math.max(0, kbwritten - writeFromCacheSize*tu_size);
                if (kbwritten <= bufferSize / 4) {
                    on = false;
                }
            } else {
                if (kbwritten >= bufferSize / 2) {
                    on = true;
                }
            }

            //error - this buffer size is not big enough
            //TODO: throw exception

            if (kbwritten > bufferSize) {
                return null;
            }
        }
        return timeUnitsOn;
    }
}
